<?php

namespace fakis\core\traits;

/**
 * 定义模型属性特性
 * 主要实现模型属性值作为动态值传入来，必须继承模型基类 `\yii\base\Model`
 *
 * @property-read array $oldAttributes 旧属性值
 * @property-read array $changedAttributes 用户当前属性值与旧属性值对比，只返回已改变的属性值
 *
 * @author Fakis <fakis738@qq.com>
 */
trait ModelAttributes
{
    /**
     * @var array
     */
    private $_attributes = [];

    /**
     * @var array
     */
    private $_oldAttributes = [];

    /**
     * {@inheritdoc}
     */
    public function attributes()
    {
        return array_keys($this->_attributes);
    }

    /**
     * 返回指定的属性是否存在
     * @param string $name 指定的属性名称
     * @return bool
     */
    public function hasAttribute($name)
    {
        return array_key_exists($name, $this->_attributes);
    }

    /**
     * 返回指定的属性值，如果属性不存在，则返回默认值
     * @param string $name 属性名称
     * @param mixed $default 默认值，默认为`null`
     * @return mixed|null
     */
    public function getAttribute($name, $default = null)
    {
        return $this->hasAttribute($name) ? $this->_attributes[$name] : $default;
    }

    /**
     * 设置指定的属性值
     * @param string $name
     * @param mixed $value
     * @return void
     */
    public function setAttribute($name, $value)
    {
        if ($this->hasAttribute($name)) {
            $this->_attributes[$name] = $value;
        }
    }

    /**
     * 定义属性
     * @param string $name the attribute name.
     * @param mixed $value the attribute value.
     */
    public function defineAttribute($name, $value = null)
    {
        $this->_attributes[$name] = $value;
    }

    /**
     * 取消定义属性
     * @param string $name the attribute name.
     */
    public function undefineAttribute($name)
    {
        unset($this->_attributes[$name]);
    }

    /**
     * 填充模型数据
     *
     * 调用 `setAttributes()` 方法后，重新设置旧属性值
     *
     * @param array $data
     *
     * @see setAttributes()
     * @see resetOldAttributes()
     */
    public function populate($data)
    {
        $this->setAttributes($data);

        // 重新设置旧属性值
        $this->resetOldAttributes();

        return $this;
    }

    /**
     * 返回旧的属性值
     * @return array
     */
    public function getOldAttributes()
    {
        return $this->_oldAttributes;
    }

    /**
     * 重新设置旧属性值
     */
    public function resetOldAttributes()
    {
        $this->_oldAttributes = $this->attributes;
    }

    /**
     * 返回已变动的属性
     * @return array
     */
    public function getChangeAttributes()
    {
        $changedAttributes = [];
        foreach ($this->attributes as $name => $value) {
            $oldAttributes = $this->getOldAttributes();
            if (array_key_exists($name, $oldAttributes) && $oldAttributes[$name] !== $value) {
                $changedAttributes[$name] = $value;
            }
        }
        return $changedAttributes;
    }

    /**
     * {@inheritdoc}
     */
    public function __get($name)
    {
        if ($this->hasAttribute($name)) {
            return $this->_attributes[$name];
        }

        return parent::__get($name);
    }

    /**
     * {@inheritdoc}
     */
    public function __set($name, $value)
    {
        if ($this->hasAttribute($name)) {
            $this->setAttribute($name, $value);
        } else {
            parent::__set($name, $value);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function __isset($name)
    {
        if ($this->hasAttribute($name)) {
            return true;
        }

        return parent::__isset($name);
    }

    /**
     * {@inheritdoc}
     */
    public function __unset($name)
    {
        if ($this->hasAttribute($name)) {
            unset($this->_attributes[$name]);
        } else {
            parent::__unset($name);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
    {
        return parent::canGetProperty($name, $checkVars, $checkBehaviors) || $this->hasAttribute($name);
    }

    /**
     * {@inheritdoc}
     */
    public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
    {
        return parent::canSetProperty($name, $checkVars, $checkBehaviors) || $this->hasAttribute($name);
    }
}